package ElectricCraft.drawer;

import ElectricCraft.model.Orientation;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.util.Stack;

/**
 *  CustomDrawer class uses Graphics2D methods to draw game elements.
 */
public class CustomDrawer extends Drawer {
	private Graphics2D graphics;
	public void setGraphics(Graphics2D graphics) {
		this.graphics = graphics;
	}

	public static final int WIRE_SIZE = 16;
	public static final int WIRE_OFFSET = (100 - WIRE_SIZE) / 2;
	public static final Color WIRE_COLOR = new Color(0.1f, 0.1f, 0.55f);

	public static final int WIRE_CROSS_SIZE = WIRE_SIZE + 16;
	public static final int WIRE_CROSS_OFFSET   = (100 - WIRE_CROSS_SIZE) / 2;

	@Override
	public void drawWire(int type, int mask) {
		pushTransform(drawPosX, drawPosY, itemSize / 100.0);

        boolean activeWE = (mask & 1) != 0;
        boolean activeNS = (type != WIRE_CROSS) ? activeWE : (mask & 2) != 0;

		graphics.setColor(activeNS ? Color.RED : WIRE_COLOR);
		graphics.fillRect(WIRE_OFFSET, WIRE_OFFSET, WIRE_SIZE, WIRE_SIZE);

		if (Orientation.checkDir(type, Orientation.NORTH)) {
			graphics.fillRect(WIRE_OFFSET, 0, WIRE_SIZE, 50);
		}
		if (Orientation.checkDir(type, Orientation.SOUTH)) {
			graphics.fillRect(WIRE_OFFSET, 50, WIRE_SIZE, 50);
		}

		if (type == WIRE_CROSS) {
			graphics.setColor(Color.WHITE);
			graphics.fillRect(WIRE_CROSS_OFFSET, WIRE_CROSS_OFFSET, WIRE_CROSS_SIZE, WIRE_CROSS_SIZE);
		}

        graphics.setColor(activeWE ? Color.RED : WIRE_COLOR);

		if (Orientation.checkDir(type, Orientation.EAST)) {
			graphics.fillRect(50, WIRE_OFFSET, 50, WIRE_SIZE);
		}
		if (Orientation.checkDir(type, Orientation.WEST)) {
			graphics.fillRect(0, WIRE_OFFSET, 50, WIRE_SIZE);
		}

		popTransform();
	}

	public static final Color DIODE_COLOR = Color.BLACK;
	private static final int DIODE_SIZE   = 40;
	private static final int DIODE_BLOCK_WIDTH = 20;
	private static final Polygon diodePoly = new Polygon(
			new int[] { -DIODE_SIZE, DIODE_SIZE - DIODE_BLOCK_WIDTH / 2, -DIODE_SIZE },
			new int[] { -DIODE_SIZE, 0, DIODE_SIZE }, 3 );

	@Override
	public void drawDiode(int orientation) {
		pushTransform(
				drawPosX + itemSize / 2,
				drawPosY + itemSize / 2,
				itemSize / 128.0,
				90 * (orientation - 1));

		graphics.setColor(DIODE_COLOR);
		graphics.fillPolygon(diodePoly);
		graphics.fillRect(DIODE_SIZE - DIODE_BLOCK_WIDTH, -DIODE_SIZE, DIODE_BLOCK_WIDTH, DIODE_SIZE * 2);

		popTransform();
	}

	private static final int KEY_SIZE = WIRE_SIZE;
	private static final int KEY_OFFSET = WIRE_SIZE / 4 + 6;

	@Override
	public void drawKey(int orientation, boolean state) {
		pushTransform(
			drawPosX + itemSize / 2,
			drawPosY + itemSize / 2,
			itemSize / 100.0,
			(orientation - 1) * 90);

		int dx = KEY_OFFSET;

		graphics.setColor(Color.BLACK);
		graphics.fillRect(-50 + dx, -KEY_SIZE / 2, 100 - 2 * dx, KEY_SIZE);

		dx += KEY_SIZE;

		graphics.setColor(Color.WHITE);
		graphics.fillRect(-50 + dx, -KEY_SIZE / 2, 100 - 2 * dx, KEY_SIZE);

		if (state) {
			dx += KEY_OFFSET / 2 + 2;

			graphics.setColor(Color.BLACK);
			graphics.fillRect(-50 + dx, -KEY_SIZE / 2, 100 - 2 * dx, KEY_SIZE);
		}

		popTransform();
	}

	private static final int POWER_SIZE = 75;

	@Override
	public void drawPower() {
		pushTransform(drawPosX, drawPosY, itemSize / 100.0);

		graphics.setColor(Color.RED);

		int offset = (100 - POWER_SIZE) / 2;
		graphics.fillOval(offset, offset, POWER_SIZE, POWER_SIZE);

		popTransform();
	}

	private static final Color INPUT_COLOR = new Color(0, 0.5f, 0);
	private static final int INPUT_SIZE = 34;

	private static final Polygon inputPoly = new Polygon(
			new int[] { -INPUT_SIZE, INPUT_SIZE, -INPUT_SIZE },
			new int[] { -INPUT_SIZE, 0, INPUT_SIZE }, 3 );

	private static final Font inputFont = new Font(Font.SERIF, Font.BOLD, 70);
    private static final int INPUT_TEXT_OFFSET_Y = -20;

	@Override
	public void drawInput(int index) {
		pushTransform(
			drawPosX + itemSize / 2,
			drawPosY + itemSize / 2,
			itemSize / 100.0);

		graphics.setColor(INPUT_COLOR);
		graphics.fillPolygon(inputPoly);

		if (index > 0) {
			String msg = "" + index;

			graphics.setFont(inputFont);
			graphics.setColor(Color.BLACK);
			Rectangle2D rect = graphics.getFont().getStringBounds(msg, graphics.getFontRenderContext());

			float offsetX = -140 + (100 - (float)rect.getWidth()) / 2;
			float offsetY = INPUT_TEXT_OFFSET_Y + (float)rect.getHeight() / 2;

			graphics.drawString(msg, offsetX, offsetY);
		}

		popTransform();
	}

	private static final Color OUTPUT_COLOR = new Color(0.5f, 0, 0);
	private static final int OUTPUT_SIZE = 30;

	@Override
	public void drawOutput(int index) {
		pushTransform(
			drawPosX + itemSize / 2,
			drawPosY + itemSize / 2,
			itemSize / 100.0);

		graphics.setColor(OUTPUT_COLOR);
		graphics.fillRect(-OUTPUT_SIZE, -OUTPUT_SIZE, OUTPUT_SIZE * 2, OUTPUT_SIZE * 2);

		if (index > 0) {
			String msg = Integer.toString(index);

			graphics.setFont(inputFont);
			graphics.setColor(Color.BLACK);
			Rectangle2D rect = graphics.getFont().getStringBounds(msg, graphics.getFontRenderContext());

			float offsetX = 36 + (100 - (float)rect.getWidth()) / 2;
			float offsetY = INPUT_TEXT_OFFSET_Y + (float)rect.getHeight() / 2;

			graphics.drawString(msg, offsetX, offsetY);
		}

		popTransform();
	}

    private static final Color CHIP_COLOR = new Color(0.27f, 0.27f, 0.27f);
    private static final int CHIP_BORDER_WIDTH = 20;

	@Override
	public void drawChipPart(int orientation) {
        pushTransform(drawPosX, drawPosY, itemSize / 100.0);

        graphics.setColor(CHIP_COLOR);
        graphics.fillRect(0, 0, 100, 100);

        boolean isInput = Drawer.isInputChipPart(orientation);
        int index = orientation & Drawer.CHIP_INDEX_MASK;
        int count = orientation >> Drawer.CHIP_COUNT_OFFSET;

        int offset = (int)(CHIP_BORDER_WIDTH * 1.5f) / 4;

        int offsetLeft  =  isInput   ? 0 : offset;
        int offsetRight = !isInput   ? 0 : offset;

        int offsetTop    = index == 0 ? 0 : offset;
        int offsetBottom = index + 1 == count ? 0 : offset;

        graphics.setColor(Color.WHITE);
        graphics.fillRect(
                CHIP_BORDER_WIDTH / 2 - offsetLeft,
                CHIP_BORDER_WIDTH / 2 - offsetTop,
                100 - CHIP_BORDER_WIDTH + offsetLeft + offsetRight,
                100 - CHIP_BORDER_WIDTH + offsetTop  + offsetBottom);

        // draw chip part number
        String msg = Integer.toString((orientation & Drawer.CHIP_INDEX_MASK) + 1);

        graphics.setColor(CHIP_COLOR);
        graphics.setFont(inputFont);
        Rectangle2D rect = graphics.getFont().getStringBounds(msg, graphics.getFontRenderContext());

        float offsetX = (100 - (float)rect.getWidth()) / 2;
        float offsetY = 50 + INPUT_TEXT_OFFSET_Y + (float)rect.getHeight() / 2;

        graphics.drawString(msg, offsetX, offsetY);

        popTransform();
	}

    private static final Dimension CHIP_PREVIEW_SIZE = new Dimension(30, 80);
    private static final Dimension CHIP_PREVIEW_PIN_SIZE = new Dimension(70, 10);
    private static final int CHIP_PREVIEW_PIN_OFFSET = 20;

    @Override
    public void drawPreviewChip() {
        pushTransform(drawPosX, drawPosY, itemSize / 100.0);

        graphics.setColor(CHIP_COLOR);

        graphics.fillRect(
                (100 - CHIP_PREVIEW_SIZE.width) / 2,
                (100 - CHIP_PREVIEW_SIZE.height) / 2,
                CHIP_PREVIEW_SIZE.width,
                CHIP_PREVIEW_SIZE.height);

        // central pin
        int pinOffset = 50 - CHIP_PREVIEW_PIN_SIZE.height / 2 - CHIP_PREVIEW_PIN_OFFSET;

        for (int k = 0; k < 3; ++k) {
            graphics.fillRect(
                    (100 - CHIP_PREVIEW_PIN_SIZE.width) / 2,
                    pinOffset,
                    CHIP_PREVIEW_PIN_SIZE.width,
                    CHIP_PREVIEW_PIN_SIZE.height);

            pinOffset += CHIP_PREVIEW_PIN_OFFSET;
        }

        popTransform();
    }

	private static final Color GRID_COLOR = Color.LIGHT_GRAY;

	@Override
	public void setClipRect(int x, int y, int width, int height) {
		Area clip     = new Area(graphics.getClip());
		Area rectArea = new Area(new Rectangle(x, y, width, height));
		clip.intersect(rectArea);

		graphics.setClip(clip);
	}

	@Override
	public void drawGrid(int countX, int countY) {
		int lenX = countX * itemSize;
		int lenY = countY * itemSize;

		++countX;
		++countY;

		graphics.setColor(Color.WHITE);
		graphics.fillRect(drawPosX, drawPosY, lenX, lenY);

		graphics.setColor(GRID_COLOR);

		for (int x = 0; x < countX; ++x) {
			int x0 = drawPosX + x * itemSize;
			graphics.drawLine(x0, drawPosY, x0, drawPosY + lenY);
		}
		for (int y = 0; y < countY; ++y) {
			int y0 = drawPosY + y * itemSize;
			graphics.drawLine(drawPosX, y0, drawPosX + lenX, y0);
		}
	}

	private static final int EDIT_GRID_OFFSET = 3;

	@Override
	public void drawEditGrid(int countX, int countY) {
		int lenX = countX * itemSize + EDIT_GRID_OFFSET * 2;
		int lenY = countY * itemSize + EDIT_GRID_OFFSET * 2;

		graphics.setColor(Color.WHITE);
		graphics.fillRoundRect(
			drawPosX - EDIT_GRID_OFFSET,
			drawPosY - EDIT_GRID_OFFSET,
			lenX, lenY,
			EDIT_GRID_OFFSET, EDIT_GRID_OFFSET);

		graphics.setColor(Color.LIGHT_GRAY);
		graphics.drawRoundRect(
			drawPosX - EDIT_GRID_OFFSET,
			drawPosY - EDIT_GRID_OFFSET,
			lenX, lenY,
			EDIT_GRID_OFFSET, EDIT_GRID_OFFSET);
	}

	private final Stack<AffineTransform> transforms = new Stack<AffineTransform>();

	@Override
	public void pushTransform(double dx, double dy) {
		AffineTransform prev = graphics.getTransform();
		transforms.push(prev);

		AffineTransform next = new AffineTransform(prev);

		next.translate(dx, dy);

		graphics.setTransform(next);
	}

	@Override
	public void pushTransform(double dx, double dy, double scale) {
		AffineTransform prev = graphics.getTransform();
		transforms.push(prev);

		AffineTransform next = new AffineTransform(prev);

		next.translate(dx, dy);
		next.scale(scale, scale);

		graphics.setTransform(next);
	}

	@Override
	public void pushTransform(double dx, double dy, double scale, double rotate) {
		AffineTransform prev = graphics.getTransform();
		transforms.push(prev);

		AffineTransform next = new AffineTransform(prev);

		next.translate(dx, dy);
		next.scale(scale, scale);
		next.rotate(rotate * Math.PI / 180);

		graphics.setTransform(next);
	}

	@Override
	public void popTransform() {
		AffineTransform prev = transforms.pop();
		graphics.setTransform(prev);
	}
}
